using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace Agile.Common
{
    /// <summary>
    /// Support functions for strings
    /// </summary>
    public class Strings
    {
        #region Contains or Doesnt

        /// <summary>
        /// This checks whether the substring is in the string
        /// </summary>
        /// <param name="searchIn">the string to look in</param>
        /// <param name="searchFor">the string we are searching for</param>
        /// <remarks>Case Insensitive</remarks>
        /// <returns>true if the substring is contained in the string</returns>
        public static bool Contains(string searchIn, string searchFor)
        {
            ArgumentValidation.CheckForNullReference(searchIn, "searchIn");
            ArgumentValidation.CheckForNullReference(searchFor, "searchFor");
            if (searchFor.Length == 0)
                throw new ArgumentException("Can't check if an empty string is a substring of another string");
            return searchIn.ToUpper(CultureInfo.CurrentCulture).IndexOf(searchFor.ToUpper(CultureInfo.CurrentCulture)) > -1;
        }

        /// <summary>
        /// This checks whether the substring is in the string array
        /// </summary>
        /// <param name="searchIn">the string array to look in.</param>
        /// <param name="searchFor">the string we are searching for</param>
        /// <remarks>Case Insensitive</remarks>
        /// <returns>true if the substring is contained in the string</returns>
        public static bool Contains(string[] searchIn, string searchFor)
        {
            ArgumentValidation.CheckForNullReference(searchIn, "searchIn");
            ArgumentValidation.CheckForNullReference(searchFor, "searchFor");
            if (searchFor.Length == 0)
                throw new ArgumentException("Can't check if an empty string is a substring of another string");
            foreach(string item in searchIn)
            {
                if (item == null) continue;
                if (item.Equals(searchFor))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Checks that the string we want to find is not in the big string
        /// </summary>
        /// <param name="searchIn">Search IN this string</param>
        /// <param name="checkForThis">search FOR this string</param>
        public static bool DoesNotContain(string searchIn, string checkForThis)
        {
            ArgumentValidation.CheckForNullReference(searchIn, "searchIn");
            ArgumentValidation.CheckForNullReference(checkForThis, "checkForThis");
            if (checkForThis.Length == 0)
                throw new ArgumentException("Can't check if an empty string is a substring of another string");
            return searchIn.ToUpper(CultureInfo.CurrentCulture).IndexOf(checkForThis.ToUpper(CultureInfo.CurrentCulture)) > -1;
        }

        #endregion

        private static int indent;
        /// <summary>
        /// log the Start time of an operation
        /// </summary>
        public static void StartTiming(string text)
        {
            Debug.WriteLine(string.Format("{2}{0} START {1}", DateTime.Now.ToString("hh:mm:ss:fff"), text, Space(indent)));
            indent++;
        }

        /// <summary>
        /// log the End time of an operation
        /// </summary>
        public static void FinishTiming(string text)
        {
            if(indent > 0)
                indent--;
            Debug.WriteLine(string.Format("{2}{0} FINISH {1}", DateTime.Now.ToString("hh:mm:ss:fff"), text, Space(indent)));
        }

        /// <summary>
        /// Removes the first instance of the given string
        /// </summary>
        /// <param name="stringToRemove">string containing comma separated columns.</param>
        /// <param name="removeFrom">remove the first instance of the string from this string</param>
        /// <returns></returns>
        public static string RemoveFirstInstanceOf(string stringToRemove, string removeFrom)
        {
            ArgumentValidation.CheckForNullReference(stringToRemove, "stringToRemove");
            ArgumentValidation.CheckForNullReference(removeFrom, "removeFrom");

            int indexOfFirstInstance = removeFrom.IndexOf(stringToRemove);
            if(indexOfFirstInstance < 0)
                return removeFrom;
            return removeFrom.Remove(indexOfFirstInstance, stringToRemove.Length);
        }

        /// <summary>
        /// Return a set of values as an array
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string[] AsStringArray(ICollection values)
        {
            var items = new string[values.Count];
            int i = 0;
            foreach (string value in values)
                items[i++] = value;
            return items;
        }

        /// <summary>
        /// Splits the value into strings, based on a large string
        /// to take out
        /// </summary>
        /// <param name="original">the original source string</param>
        /// <param name="delimiter">the delimiter to split on</param>
        /// <returns>the results</returns>
        public static string[] Split(string original, string delimiter)
        {
            ArgumentValidation.CheckForNullReference(original, "original");
            ArgumentValidation.CheckForNullReference(delimiter, "delimiter");

            var list = new ArrayList();
            string scanString = original.ToUpper(CultureInfo.CurrentCulture);
            string scanFor = delimiter.ToUpper(CultureInfo.CurrentCulture);
            int currentPosition = 0;
            int nextMatch = scanString.IndexOf(scanFor, currentPosition);
            while (nextMatch > -1)
            {
                list.Add(original.Substring(currentPosition, nextMatch - currentPosition));
                currentPosition = nextMatch + delimiter.Length;
                if (currentPosition >= original.Length)
                    nextMatch = -1;
                else
                    nextMatch = scanString.IndexOf(scanFor, currentPosition);
            }
            list.Add(original.Substring(currentPosition));
            return AsStringArray(list);
        }


        /// <summary>
        /// Get a string with this amount of spaces
        /// </summary>
        /// <param name="count">the count</param>
        /// <returns>the string</returns>
        public static string Space(int count)
        {
            return new string(' ', count);
        }

        /// <summary>
        /// Gets an objet as a string, but doesn't spit the dummy if the object
        /// happens to be null
        /// </summary>
        /// <param name="value">the value</param>
        /// <returns>the value, or an empty string</returns>
        public static string ToString(object value)
        {
            return (value == null) ? string.Empty : value.ToString();
        }


        /// <summary>
        /// Return the string with quotes around it
        /// </summary>
        /// <param name="value">the value</param>
        /// <returns>the quoted string</returns>
        public static string Quoted(string value)
        {
            ArgumentValidation.CheckForNullReference(value, "value");
            return "\"" + value + "\"";
        }

        /// <summary>
        /// Get the leftmost n characters
        /// </summary>
        /// <param name="value">the string we are taking a substring of</param>
        /// <param name="count">the count of characters</param>
        /// <returns>the leftmost n chracters</returns>
        public static string Left(string value, int count)
        {
            ArgumentValidation.CheckForNullReference(value, "value");
            return value.Substring(0, Math.Min(count, value.Length));
        }

        /// <summary>
        /// Returns whether the string is decimal or not
        /// Does not accept commas in the string
        /// </summary>
        /// <param name="check">Any string</param>
        /// <returns>The truth of the matter!</returns>
        public static bool IsValidNumber(string check)
        {
            if ((check == null) || (check.Length == 0))
                return false;
            return Regex.Match(check, @"^-?\d*(\.\d+)?$").Success;
        }


        /// <summary>
        /// Converts the given number to a string and appends the appropriate ordinal.
        /// </summary>
        /// <param name="number">any valid number</param>
        public static string ConvertWithOrdinal(int number)
        {
            // Special cases
            switch (number)
            {
                case 11:
                case 12:
                case 13:
                    return string.Format(CultureInfo.CurrentCulture, "{0}th", number.ToString(CultureInfo.CurrentCulture));
            }

            var remainder = (int) decimal.Remainder(number, 10);
            switch (remainder)
            {
                case 1:
                    return string.Format(CultureInfo.CurrentCulture, "{0}st", number.ToString(CultureInfo.CurrentCulture));
                case 2:
                    return string.Format(CultureInfo.CurrentCulture, "{0}nd", number.ToString(CultureInfo.CurrentCulture));
                case 3:
                    return string.Format(CultureInfo.CurrentCulture, "{0}rd", number.ToString(CultureInfo.CurrentCulture));
                default:
                    return string.Format(CultureInfo.CurrentCulture, "{0}th", number.ToString(CultureInfo.CurrentCulture));
            }
        }

        /// <summary>
        /// Removes any spaces from the given string (concatenates each word in the string)
        /// </summary>
        public static string RemoveSpacesFromString(string removeFrom)
        {
            string delimeter = " ";
            string[] split = null;

            split = removeFrom.Split(delimeter.ToCharArray());
            removeFrom = "";
            foreach (string s in split)
            {
                removeFrom += s.Trim();
            }
            return removeFrom;
        }

        /// <summary>
        /// Removes any unsafe characters from the given string, i.e. anything that is not a letter or a number!
        /// Note: Underscores "_" are NOT removed, spaces " " ARE removed.
        /// </summary>
        public static string RemoveNonAlphanumericCharacters(string removeFrom)
        {
            return Regex.Replace(removeFrom, @"\W*", "");
        }

        /// <summary>
        /// Gets the string that is between the start and finish strings.
        /// NOTE: Always works from the FIRST OCCURENCE of start and finish
        /// </summary>
        /// <param name="searchIn">Search in this string</param>
        /// <param name="start">Starting from the end of this string</param>
        /// <param name="finish">Get the contents up the start of this string</param>
        /// <remarks>Does not return any of the start and finish strings</remarks>
        public static string GetStringBetween(string searchIn, string start, string finish)
        {
            ArgumentValidation.CheckForNullReference(searchIn, "searchIn");
            ArgumentValidation.CheckForNullReference(start, "start");
            ArgumentValidation.CheckForNullReference(finish, "finish");

            if(!searchIn.Contains(start))
                return string.Empty;

            int indexOfStartString = searchIn.IndexOf(start);
            int startPosition = indexOfStartString + start.Length;
            // make sure - only search AFTER the index of the startPosition
            int endPosition = searchIn.Substring(startPosition).IndexOf(finish);
            // need the index of the end position relative to the entire original string
            endPosition += startPosition;

            int lengthOfStringToReturn = endPosition - startPosition;

            if ((startPosition < 0) || (endPosition < 0))
                return string.Empty;

            return searchIn.Substring(startPosition
                                      , lengthOfStringToReturn);
        }


        /// <summary>
        /// Gets the string that is between the start and finish strings,
        /// INCLUDING the start and finish strings.
        /// NOTE: Always works from the FIRST OCCURENCE of start and finish
        /// </summary>
        /// <param name="searchIn">Search in this string</param>
        /// <param name="start">Starting from the start of this string</param>
        /// <param name="finish">Get the contents up the end of this string</param>
        public static string GetStringBetweenInclusive(string searchIn, string start, string finish)
        {
            return GetStringBetweenInclusive(searchIn, start, finish, string.Empty);
        }

        /// <summary>
        /// Gets the string that is between the start and finish strings,
        /// INCLUDING the start and finish strings.
        /// NOTE: Always works from the FIRST OCCURENCE of start and finish
        /// </summary>
        /// <param name="searchIn">Search in this string</param>
        /// <param name="start">Starting from the start of this string</param>
        /// <param name="finish">Get the contents up the end of this string</param>
        /// <param name="splitter">split the start and finish strings with this if 
        /// there is nothing between the them. (e.g. you may want a new line '\r\n')</param>
        public static string GetStringBetweenInclusive(string searchIn, string start
                                                       , string finish, string splitter)
        {
            string stringBetween = GetStringBetween(searchIn, start, finish);
            if (stringBetween.Trim() == string.Empty)
                stringBetween = splitter;

            return start + stringBetween + finish;
        }

        /// <summary>
        /// Splits the given string by new line, then places the first characters of every 
        /// line in front of each split and returns the result.
        /// NOTE: also starts with a new line
        /// </summary>
        /// <param name="stringToSplit">the single or multi line string that is to be split</param>
        /// <param name="firstCharactersOfEveryLine">characters that will appear as the first characters of every line.</param>
        /// <remarks>particularly useful for generating comments.</remarks>
        public static string SplitMultiLineString(string stringToSplit, string firstCharactersOfEveryLine)
        {
            string newline = "\n";
            string[] splitString = stringToSplit.Split(newline.ToCharArray());
            StringBuilder splitStringToReturn = new StringBuilder();

            foreach (string splitComment in splitString)
            {
                splitStringToReturn.Append(string.Format(@"
{0}{1}", firstCharactersOfEveryLine, splitComment.Trim()));
            }
            return splitStringToReturn.ToString();
        }


        /// <summary>
        /// Returns the given string with 'camel casing'
        /// </summary>
        /// <example>'SomeString' returns 'someString'.
        /// NOTE: at the moment 'Somestring' should return 'someString' but it currently will only return 'somestring'
        /// (this should change in the future).</example>
        /// <param name="convertToCamelCase">the string to be converted to camel casing.</param>
        /// <returns></returns>
        public static string ToCamelCase(string convertToCamelCase)
        {
            if (convertToCamelCase.Equals(string.Empty))
                return String.Empty;
            string firstCharacter = convertToCamelCase.Substring(0, 1);
            var builder = new StringBuilder(convertToCamelCase);
            return builder.Replace(firstCharacter, firstCharacter.ToLower(), 0, 1).ToString();
        }

        /// <summary>
        /// Returns the given string with 'Pascal casing'
        /// </summary>
        /// <example>'someString' returns 'SomeString'.
        /// NOTE: at the moment 'somestring' should return 'SomeString' but it currently will only return 
        /// 'somestring' (this should change in the future).</example>
        /// <param name="convertToPascalCase">the string to be converted to pascal casing.</param>
        /// <returns></returns>
        public static string ToPascalCase(string convertToPascalCase)
        {
            if (convertToPascalCase.Equals(string.Empty))
                return String.Empty;
            string firstCharacter = convertToPascalCase.Substring(0, 1);
            var builder = new StringBuilder(convertToPascalCase);
            return builder.Replace(firstCharacter, firstCharacter.ToUpper(), 0, 1).ToString();
        }

        #region Parse Date string

        /// <summary>
        /// Culture for AUS
        /// </summary>
        private static readonly CultureInfo Cult = new CultureInfo("en-AU");

        /// <summary>
        /// Takes a string and format and returns in that format
        /// </summary>
        /// 
        /// <param name="date">Date to format</param>
        /// <param name="format">Format to apply</param>
        /// <param name="defaultVal">The default value to apply</param>
        /// 
        /// <returns>string</returns>
        public static string ParseDateString(string date, string format, string defaultVal)
        {
            // check params
            if (date == string.Empty || format == string.Empty)
            {
                return defaultVal;
            }

            // return empty if empty date
            if (DateTime.MinValue.ToString() == date)
            {
                return defaultVal;
            }

            // return empty if not a date
            if (! Dates.IsDate(date))
            {
                return defaultVal;
            }
            // now return the date in the correct format
            return DateTime.Parse(date).ToString(format, Cult.DateTimeFormat );
        }

        /// <summary>
        /// Gets the argument value for the given switch
        /// </summary>
        /// <param name="args"></param>
        /// <param name="switchValue">The full switch to search for (including ALL characters) e.g. must be '/e' or '/d' for the database update program.</param>
        /// <returns></returns>
        public static string GetArgument(IEnumerable<string> args, string switchValue)
        {
            foreach (string item in args)
            {
                if (item.StartsWith(switchValue))
                {
                    return item.Substring(switchValue.Length).Trim();
                }
            }
            return string.Empty;
        }
        #endregion Methods
    }
}